Calculando Estatisticas Resumidas

Agregando os seus Dados

Estatísticas Resumidas (summarize)

Nos últimos tutoriais, não mexemos com a unidade de análise de nosso banco de dados - sempre avaliamos os voos individuais, por exemplo. Porém, mesmo depois de vários filtros e mutates, os seus dados provavalmente tem dezenas, centenas ou milhares de linhas - é difícil incluir toda esta informação no seu relatório, e impossível para o leitor entender tudo. É por isso que usamos estatísticas resumidas: médias, medianas etc.

Para gerar um número único que descreve uma variável em nossa tabela, usamos o verbo summarize() que funciona perfeitamente dentro do fluxo da nossa análise com o pipe (%>%). O summarize() gera um novo tibble (tabela/data.frame) pequena para conter as estatísticas resumidas, abandonando o nosso tibble original.

A função exige três elementos (i) o nome da nova variável no novo tibble; (ii) a função que vai agregar/resumir a variável, e (iii) a variável que será resumida. Veja o exemplo abaixo, que calcula a distância média de todos os voos.

library("nycflights13")
library("tidyverse")
flights %>% summarize(media_distance=mean(distance))
# A tibble: 1 x 1
  media_distance
           <dbl>
1          1040.

Fácil, sim? Usando a variedade das funções estatísticas em R (ou qualquer pacote adicional), pode calcular qualquer estatística que te interesse. Experimente com os exemplos na tabela.

Estatística Função em R
Média mean()
Mediana median()
Desvio padrão sd()
Quantil quantile(var, probs=0.1)

A nossa nova tabela agregada pode conter mais de uma estatística resumida, cada uma em uma coluna nova:

flights %>% summarize(media_distance=mean(distance),
                                      mediana_distance=median(distance),
                                      sd_distance=sd(distance))

É comum incorporar estatísticas resumidas no texto do nosso documento de R Markdown. Lembre-se de ‘in-line’ código, usando `r ` fora do chunk? Como podemos inserir a nossa estatística no texto do relatório? O resultado de summarize() ainda é um tibble, e uma tabela não cabe em um parágrafo. Temos que transformar o valor no tibble em um valor único.

Uma função bastante útil aqui é pull() (tirar). Ele transforma uma variável de um tibble para um vetor, e quando o vetor tem apenas um elemento (ou seja, o tibble tem apenas uma linha), o resultado é um valor único, perfeito para inserir em in-line código:

estatisticas <- flights %>% summarize(media_distance=mean(distance),
                                      mediana_distance=median(distance),
                                      sd_distance=sd(distance))

media_distance <- estatisticas %>% pull(media_distance)

Agora, posso gera a frase no relatório que se refere ao valor de media_distance:

"A distância média dos voos é `r media_distance`.

“A distância média dos voos é 1039.9126036”.

Habilidade Básica de Programação: Excluindo NAs

Vamos tentar mais uma estatística:

flights %>% summarize(dep_delay=mean(dep_delay))

Qual foi o resultado? NA? O que significa dados faltandos aqui? O padrão em R é que o R reclama quando tem um potential por erros, forçando você a investigar. Isso pode ser chato as vezes, mas no longo prazo é uma medida necessária para garantir que você entende o que você está calculando. Neste caso, na presença de pelo menos um valor NA na variável que estamos resumido, o R passa este NA para o resultado final, mesmo que existe milhares de outros valores prontos para ser resumidos. Isso é o nosso sinal que os nossos dados contém lucanas, e temos que deixar explícito para R como a tratar estes casos. Por enquanto vamos ignorar eles usando o argumento na.rm=TRUE, e calcular a estatística resumida apenas com os dados restantes:

flights %>% summarize(dep_delay=mean(dep_delay,na.rm=TRUE))


Habilidade Básica de Programação: Funções Novas

O R é muito flexível - se você quiser uma agregação não disponível em uma função atual, pode gerar a sua própria função. Escrever uma função depends de um formato padrão - um nome pela função, os insumos que a função recebe como argumento, e o objeto que quer devolver como resultado da função.

nome_funcao <- function(insumo1, insumo2){

  resultado <- ...
  
  return(resultado)
    
}

Imagine-se que quissemos calcular a razão entre o percentil 90 e o percentil 10. Não existe uma função pronto para calcular isso, então então vamos escrever nós mesmos.

percentile_90_10 <- function(variavel) {
  
  calculo <- quantile(variavel, probs=0.9,na.rm=TRUE)/
    quantile(variavel, probs=0.1,na.rm=TRUE)
    
    return(calculo)
}

Note que esta função aceita um vetor (apenas uma coluna do nosso tibble), e devolve um valor único, indicado por return(calculo). Vamos aplicar a nossa nova função:

flights %>% summarize(percentile_90_10_distance=percentile_90_10(distance),
                      percentile_90_10_air_time=percentile_90_10(air_time))

Isto é programação. Agora sabemos como a trabalhar com os dois elementos fundamentais: objetos (data.frames/tibbles etc.) e funções. Tudo em R é uma combinação de objetos (substantivos) com funções (verbos) para criar a nossa receita de análise.

Grupos (group_by)

Quase sempre, os nossos dados estão organizados em grupos e sub-grupos, pode ser anos, meses e dias, aeroportos, ou países. Frequentemente, nos queremos as estatísticas resumidas por ano, ou por país. O poder de summarize() é ampliado exponentialmente quando os resumos/agregações são feitos no nível de grupos e não para o banco de dados inteiro. O que define os grupos? Uma outra variável em nossa tabela.

Dado que podemos criar vários níveis/tipos de agrupamentos de nossos dados, temos que especificar quais nos queremos. Para definir os grupos relevantes, podemos criar um ‘grouped tibble’ usando o verbo group_by():

flights_por_aeroporto <- flights %>% group_by(origin)

Qual o resultado, flights_por_aeroporto, e como difere do banco de dados original de flights? Parece ígual! O número de linhas e colunas é ígual, nada mudou…Se digitamos o nome do novo objeto flights_por_aeroporto no ‘Console’ no canto baixo do RStudio, podemos ver uma pequena diferença: existe uma linha Groups: origin [3] que não existe no banco de dados original de flights. Este ‘3’ siginfica os três aeroportos de origem nos dados que usamos para agrupamento.

Na prática, group_by() sozinho não é útil para nada. Temos que combinar com mais uma função subsequente para gerar resultados interessantes. Por exemplo, vamos calcular a média da distância por aeroporto:

flights %>% group_by(origin) %>% 
  summarize(mean_distance=mean(distance))

Agora, a nova tabela de resumo tem três linhas, uma para cada aeroporto. Os três grupos correspondem aos três estatísticas resumidas. Note que não mudamos nada no summarize() do último vez - só temos mais um verbo em nosso pipe, o group_by().

Os argumentos de group_by() são sempre as variáveis de agrupamento, e podem ser vários:

flights %>% group_by(origin, month) %>% 
  summarize(mean_distance=mean(distance))

Quantas linhas têm o resultado? Porque 36? Porque pedimos agrupamento por origem (3 possibilidades) e mês (12 possibilidades): \(3*12=36\). A unidade de análise na tabela final é o aeroporto-mês.

Note que o resultado de summarize() é sempre um tibble, então ele não precisa terminar o nosso fluxo de análise - podemos continuar processando o resultado de summarize() com todas as funções que já acustamamos usar. Por exemplo, podemos filtrar ou mutate para criar uma tabela apropriado para incluir em nosso relatório:

flights %>% group_by(origin, month) %>% 
  summarize(mean_distance=mean(distance)) %>%
  filter(origin!="LGA") %>%
  mutate(mean_distance_km=mean_distance*1.60934)

Número do observações por Grupo (tally)

Uma aplicação enormamente útil de group_by() é para calcular o número de observações (linhas) em cada grupo do banco de dados.

flights %>% group_by(origin) %>% 
  tally()

Assim, é fácil comparar o número de voos em cada aeroporto. A função tally não precisa de argumentos.

Quantos voos decolaram de cada aeroporto origem para cada destino?

flights %>% group_by(origin, dest) %>% 
  tally()

Mutate por Grupo

Não é apenas resumos que conseguimos executar por grupo. É comum também aplicar um mutate() por grupo. Esta combinação fornece muita flexibilidade e poder. Por exemplo, se quiser manter o tamanho e a unidade de análise do seu banco de dados original, e inserir a média do grupo como coluna, pode executar assim:

flights %>% group_by(origin) %>%
  mutate(media_distance=mean(distance,na.rm=TRUE))

Confirme no tibble resultante que o número de linhas não mudou, e que a média distância é ígual para todos os voos do mesmo aeroporto, e varia entre aeroportos.

Qual a diferença conceitual entre summarize() e mutate()?

  1. summarize() sempre reduz o número de linhas no banco de dados - é uma agregação total ou por grupo.

  2. mutate() nunca reduz (ou aumenta) o número de linhas no banco de dados - apenas adiciona uma nova coluna.

Percentagens

Um dos pedidos mais comuns é calcular porcentagens em R. Tome cuidado: já vi muitos cálculos errados, mesmo que uma porcentagem é uma variável simples. O chave é que temos que definir o denominador da fórmula de porcentagem:

\[% = \frac{Valor}{Total do grupo relevante}*100 \]

Queremos uma porcentagem para cada observação no banco de dados, então isso exige um mutate() Por exemplo, se quisemos calcular a percentagem da distância de cada voo na distância total de todos os voos:

flights %>% mutate(Pct_distance=100*(distance/sum(distance,na.rm=TRUE)))

Note que o sum() aqui está somando a distância de todas as observações. Claro que cada voo é uma porcentagem pequena do total na última coluna. Se quisemos a porcentagem da distância de cada voo no total de cada mês, temos que limitar o escopode sum() apenas para as outras observações no mesmo mês. O group_by() facilita isso:

flights %>% group_by(month) %>% 
  mutate(Pct_distance_month=100*(distance/sum(distance,na.rm=TRUE)))

Agora a última coluna reflete a porcentagem de distância de cada voo no total de milhas de voos no mesmo mês. Podemos ser mais específico ainda, limitando o denominador e aumentado a porcentagem resultante:

flights %>% group_by(month, day, hour, origin) %>% 
  mutate(Pct_distance_month=100*(distance/sum(distance,na.rm=TRUE)))

A variável nova agora mede: Entre todos os voos que decolaram no mesmo mês, mesmo dia e mesma hora, no mesmo aeroporto, qual porcentagem da distância voada contribuiu este voo específico?

Finalmente, é comum calcular a porcentagem do número de observações (linhas) em um grupo comparado com o total. Neste caso, calculamos a porcentagem não baseado em uma variável, mais baseado no número de linhas. O fluxo de trabalho recomendado é:

flights %>% group_by(origin) %>% 
  tally() %>%
  mutate(Pct_por_aeroporto=n/sum(n))

Se quiser calcular a porcentagem de voos por mês em cada aeroporto separado, podemos usar dois processos de agrupamento, primeiro para calcular o número de observações por aeroporto-mês, e segundo para definir o denominador como o aeroporto para o cálculo de porcentagem:

flights %>% group_by(origin, month) %>% 
  tally() %>%
  group_by(origin) %>% 
  mutate(Pct_por_mes_no_aeroporto=n/sum(n))

Saindo de Agrupamentos (ungroup)

Resumos e Transformações de Múltiplas Colunas (summarize_all)

Uma limitação de summarize() é que temos que pedir a média de cada variável separadamente. Se tivermos dezenas de variáveis, isso exige muito código. Não há jeito de calcular a média de todas as variáveis? Há sim! Podemos usar summarize_all():

flights %>% summarize_all(mean,na.rm=TRUE)

Salvamos 18 linhas de código comparado com o uso de summarize para cada variável individualmente! O sintaxe de summarize_all() é um pouco diferente: nos parenteses, especificamos o nome da função/estatística sem parenteses (mean), e depois da vírgula qualquer outro argumento à função mean (aqui na.rm=TRUE). Não precisamos especificar as variáveis para resumir; estamos resumindo todos.

Note que mean apenas funciona para variáveis numéricas; ele devolve NA para variáveis do tipo caractere ou factor.

Transformações de Colunas específicas (mutate_all, mutate_at, mutate_if)

Podemos tentar o mesmo com mutate para manter a unidade de análise do banco de dados original, mas transformar cada variável com a mesma transformação. Por exemplo, na estatística frequentemente queremos padronizar cada variável, subtraindo a média e dividindo pelo desvio padrão. Esta função simples já existe, se chama scale. Vamos aplicar ao banco de dados flights:

flights %>% mutate_all(scale)

Não funcionou. Qual foi o erro? Aqui, mutate_all não é suficientemente flexível para pular as colunas que contém caractares infelizmente. Agora temos três alternativas. Podemos usar select() para selecionar as colunas númericos manualmente antes de rodar mutate_all():

flights %>%
    select(year, month, day, dep_time, sched_dep_time, dep_delay, arr_time,
        sched_arr_time, arr_delay, flight, air_time, distance, hour, minute) %>%
    mutate_all(scale)

Deu certo, mas um pouco chato para digitar. A segunda opção usa uma outra variadade de mutate se chama mutate_at(), que permite especificar as variáveis dentro da função (e dentro de uma mini-função se chama vars()):

flights %>%
    mutate_at(vars(year, month, day, dep_time, sched_dep_time, dep_delay,
        arr_time, sched_arr_time, arr_delay, flight, air_time, distance,
        hour, minute), scale)
# A tibble: 336,776 x 20
   year[,1] month[,1] day[,1] dep_time[,1] sched_dep_time[,1]
      <dbl>     <dbl>   <dbl>        <dbl>              <dbl>
 1      NaN     -1.63   -1.68        -1.70              -1.77
 2      NaN     -1.63   -1.68        -1.67              -1.74
 3      NaN     -1.63   -1.68        -1.65              -1.72
 4      NaN     -1.63   -1.68        -1.65              -1.71
 5      NaN     -1.63   -1.68        -1.63              -1.59
 6      NaN     -1.63   -1.68        -1.63              -1.68
 7      NaN     -1.63   -1.68        -1.63              -1.59
 8      NaN     -1.63   -1.68        -1.62              -1.59
 9      NaN     -1.63   -1.68        -1.62              -1.59
10      NaN     -1.63   -1.68        -1.62              -1.59
# ... with 336,766 more rows, and 15 more variables:
#   dep_delay <dbl[,1]>, arr_time <dbl[,1]>,
#   sched_arr_time <dbl[,1]>, arr_delay <dbl[,1]>, carrier <chr>,
#   flight <dbl[,1]>, tailnum <chr>, origin <chr>, dest <chr>,
#   air_time <dbl[,1]>, distance <dbl[,1]>, hour <dbl[,1]>,
#   minute <dbl[,1]>, time_hour <dttm>, dep_delay_dobro <dbl>

A terceira opção é a mais eficiente e vai identificar automaticamente as colunas númericas - esta variedade final de mutate, mutate_if() aceita um ‘teste’ em vez de uma lista de variáveis para ser transformadas. O teste é uma função simples que devolve o resultado TRUE ou FALSE para cada coluna. Em nosso caso, queremos testar se a coluna seja númerico, então vamos usar o teste (a função) is.numeric:

flights %>% mutate_if(is.numeric, scale)
# A tibble: 336,776 x 20
   year[,1] month[,1] day[,1] dep_time[,1] sched_dep_time[,1]
      <dbl>     <dbl>   <dbl>        <dbl>              <dbl>
 1      NaN     -1.63   -1.68        -1.70              -1.77
 2      NaN     -1.63   -1.68        -1.67              -1.74
 3      NaN     -1.63   -1.68        -1.65              -1.72
 4      NaN     -1.63   -1.68        -1.65              -1.71
 5      NaN     -1.63   -1.68        -1.63              -1.59
 6      NaN     -1.63   -1.68        -1.63              -1.68
 7      NaN     -1.63   -1.68        -1.63              -1.59
 8      NaN     -1.63   -1.68        -1.62              -1.59
 9      NaN     -1.63   -1.68        -1.62              -1.59
10      NaN     -1.63   -1.68        -1.62              -1.59
# ... with 336,766 more rows, and 15 more variables:
#   dep_delay <dbl[,1]>, arr_time <dbl[,1]>,
#   sched_arr_time <dbl[,1]>, arr_delay <dbl[,1]>, carrier <chr>,
#   flight <dbl[,1]>, tailnum <chr>, origin <chr>, dest <chr>,
#   air_time <dbl[,1]>, distance <dbl[,1]>, hour <dbl[,1]>,
#   minute <dbl[,1]>, time_hour <dttm>, dep_delay_dobro <dbl[,1]>

Veja a beleza de programação mais eficiente - o mesmo resultado com menos digitação! Como interpretamos o código? “Pegue o banco de dados de flights, depois aplica uma transformação às variáveis se a variável is.numeric (é númerica), e a transformação é scale.”

Existe também as versões equivalentes destas funcões para summarize: summarize_all, summarize_at e summarize_if.